#include<jni.h>
#include <android/log.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "matrix.h"
#include "nmath/funct.h"
#include "physic.h"

/* =========== Utilities ======== */
jfieldID java_get_field_by_classname(JNIEnv *env, char *className, JavaField field){
	jclass cls = (*env)->FindClass(env, className);
	jfieldID fieldId = (*env)->GetFieldID(env, cls, field.name, field.signature);
	(*env)->DeleteLocalRef(env, cls);
	return fieldId;
}

jfieldID java_get_field_by_class(JNIEnv *env, jclass cls, JavaField field){
	return ( (*env)->GetFieldID(env, cls, field.name, field.signature) );
}

jfieldID java_get_field(JNIEnv *env, jclass cls, char *field_name, char *signature){
	return ( (*env)->GetFieldID(env, cls, field_name, signature) );
}

jmethodID java_get_method(JNIEnv *env, char *className, JavaMethod method){
	jclass cls = (*env)->FindClass(env, className);
	jmethodID methodId = (*env)->GetMethodID(env, cls, method.name, method.signature);
	(*env)->DeleteLocalRef(env, cls);
	return methodId;
}

jmethodID java_get_method_by_class(JNIEnv *env, jclass cls, JavaMethod method){
	return (*env)->GetMethodID(env, cls, method.name, method.signature);
}

Function *getFunction(JNIEnv *env, jobject thiz){
	Function *f = NULL;
	int addr;
	jfieldID nativeId;
	jclass clazz = (*env)->FindClass(env, FunctionClassPath);
	nativeId = java_get_field(env, clazz, "nativeFunction", "I");
	addr = (*env)->GetIntField(env, thiz, nativeId);
	f = (Function *)addr;
	return f;
}

void setErrorPosition(JNIEnv *env, jobject thiz, int pos){
	jclass clazz;
	jfieldID errorPosField;
	clazz = (*env)->FindClass(env, FunctionClassPath);
	if(clazz != NULL){
		errorPosField = java_get_field(env, clazz, "errorPos", "I");
		(*env)->SetIntField(env, thiz, errorPosField, (jint)pos);
	}
	(*env)->DeleteLocalRef(env, clazz);
}

/*========= JNI Methods =============*/
void jni_initialize(JNIEnv *env, jobject thiz){
	jclass clazz;
	jfieldID native_field;
	Function *f = (Function*)malloc(sizeof(Function));
	clazz = (*env)->FindClass(env, FunctionClassPath);
	if(clazz != NULL){
		native_field = java_get_field(env, clazz, "nativeFunction", "I");
		(*env)->SetIntField(env, thiz, native_field, (jint)f);
	}
	(*env)->DeleteLocalRef(env, clazz);
}

int jni_setFunction(JNIEnv *env, jobject thiz, jstring jstrFunction, jarray jarrVars){
	int error, eIdx = -1;
	char vars[4];
	Function *f;

	int varlen = (*env)->GetArrayLength(env, jarrVars);
	jchar *jvars = (*env)->GetCharArrayElements(env, jarrVars, NULL);
	const char *strf = (*env)->GetStringUTFChars(env, jstrFunction, NULL);

	if(vars != NULL){
		/* Copy to v and release var array */
		for(error = 0; error<varlen; error++)
			vars[error] = (char)(jvars[error]);
		vars[error] = 0;
		(*env)->ReleaseCharArrayElements(env, jarrVars, jvars, JNI_ABORT);

		f = getFunction(env, thiz);
		initFunct(f);
		reset(f, strf, &error);
		if(error != 0){
			//set position that error has occurred to Java object
			(*env)->ReleaseStringUTFChars(env, jstrFunction, strf);
			return error;
		}

		setVariables(f, vars, varlen);
		error = parseFunct(f, &eIdx);
		if(error != 0){
			(*env)->ReleaseStringUTFChars(env, jstrFunction, strf);
			setErrorPosition(env, thiz, eIdx);
			return error;
		}

		reduce(f, &error);
		if(error != 0){
			(*env)->ReleaseStringUTFChars(env, jstrFunction, strf);
			return error;
		}

	}
	(*env)->ReleaseStringUTFChars(env, jstrFunction, strf);

	return 0;
}

double jni_f(JNIEnv *env, jobject thiz, jarray jarrValues){
	RParam rp;
	Function *f;

	jint len = (*env)->GetArrayLength(env, jarrValues);
	jfloat *values = (*env)->GetFloatArrayElements(env, jarrValues, NULL);

	rp.retv = 0;
	rp.error = -1;
	if(values != NULL){
		f = getFunction(env, thiz);
		rp.error = 0;
		rp.t = *(f->prefix);
		rp.values = values;
		rp.variables = f->variable;
		calc_t((void*)&rp);
	}
	(*env)->ReleaseFloatArrayElements(env, jarrValues, values, JNI_ABORT);
	return rp.retv;
}

jstring jni_get_derivative(JNIEnv *env, jobject thiz, jchar dvar){
	Function *f = getFunction(env, thiz);
	jstring deri;
	int error = -1;
	TNode *df;
	DParam dp;

	dp.t = *(f.prefix);
	dp.x = dvar;
	df = (TNode *)derivative(&dp);
	dp.t = df;
	dp.error = 0;
	reduce_t(&dp);
}

void jni_release(JNIEnv *env, jobject thiz){
	Function *f = NULL;
	int addr;
	jfieldID nativeId;
	jclass clazz = (*env)->FindClass(env, FunctionClassPath);
	nativeId = java_get_field(env, clazz, "nativeFunction", "I");
	addr = (*env)->GetIntField(env, thiz, nativeId);
	f = (Function *)addr;

	/* Release Function object if any */
	releaseFunct(f);
	free(f);
}
